home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue45 / Alfresco / AALZCmpr.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2000-11-02  |  10.5 KB  |  322 lines

  1. {*********************************************************}
  2. {* AALZCmpr                                              *}
  3. {* Copyright (c) Julian M Bucknall 1998-1999             *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Algorithms Alfresco LZ77 unit - Compress/decompress   *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AALZCmpr;
  14.  
  15. interface
  16.  
  17. uses
  18.   SysUtils,
  19.   Classes,
  20.   AALZBase;
  21.  
  22. procedure AALZCompress(aInStream, aOutStream : TStream);
  23.   {Compress the input stream to the output stream using LZ77 compression}
  24.  
  25. procedure AALZDecompress(aInStream, aOutStream : TStream);
  26.   {Decompress the input stream to the output stream using LZ77 compression}
  27.  
  28. implementation
  29.  
  30. {Notes: A distance/length encoding consists of two parts: the distance
  31.         and the length. The distance varies from 1 to 8192, and the
  32.         length from 3 to 10. To store both these values into a 16-bit
  33.         entity, we subtract 1 from the distance value (to force it in
  34.         the range 0 to 8191, or $0000 to $1FFF) and shift it left by 3
  35.         bits. We subtract 3 from the length (to force it in the range
  36.         0 to 7) and add it to the modified distance value. The
  37.         resulting 16-bit value is then written.
  38.         To unpack the two values on reading, perform the opposite. AND
  39.         $7 to the 16-bit value, and add 3 for the length. Shift the
  40.         16-bit value right by 3 bits and add 1 for the distance.}
  41.  
  42. uses
  43.   AALZSWin,
  44.   AALZHash;
  45.  
  46. const
  47.   AALZHeader = $5A4C4141; {the header for the compressed result}
  48.  
  49. type
  50.   PEnumExtraData = ^TEnumExtraData;    {extra data record for the    }
  51.   TEnumExtraData = packed record       {  hash table's FindAll method}
  52.     edSW          : TaaLZSlidingWindow;{..sliding window object}
  53.     edMaxLen      : integer;           {..maximum match length so far}
  54.     edDistMaxMatch: integer;           {..distance of max match}
  55.   end;
  56.  
  57. type
  58.   TEncoding = packed record
  59.     AsDistLen : integer;
  60.     AsChar    : char;
  61.     IsChar    : boolean;
  62.     {$IFDEF WIN32}
  63.     Filler    : word;
  64.     {$ENDIF}
  65.   end;
  66.   TEncodingArray = array [0..7] of TEncoding;
  67.  
  68. {===Stream read/write routines with exceptions=======================}
  69. procedure StreamRead(aStream : TStream;
  70.                  var aBuffer;
  71.                      aBufLen : integer);
  72. var
  73.   BytesRead : longint;
  74. begin
  75.   BytesRead := aStream.Read(aBuffer, aBufLen);
  76.   if (BytesRead <> aBufLen) then
  77.     raise Exception.Create('Stream read error: not enough data read');
  78. end;
  79. {--------}
  80. procedure StreamWrite(aStream : TStream;
  81.                   var aBuffer;
  82.                       aBufLen : integer);
  83. var
  84.   BytesWrit : longint;
  85. begin
  86.   BytesWrit := aStream.Write(aBuffer, aBufLen);
  87.   if (BytesWrit <> aBufLen) then
  88.     raise Exception.Create('Stream write error: not enough data written (disk full?)');
  89. end;
  90. {====================================================================}
  91.  
  92.  
  93. {===Helper routines==================================================}
  94. procedure MatchLongest(aExtraData : pointer;
  95.                  const aKey       : TaaLZKey;
  96.                        aOffset    : longint); far;
  97. var
  98.   Len  : integer;
  99.   Dist : integer;
  100. begin
  101.   with PEnumExtraData(aExtraData)^ do begin
  102.     Len := edSW.Compare(aOffset, Dist);
  103.     if (Len > edMaxLen) then begin
  104.       edMaxLen := Len;
  105.       edDistMaxMatch := Dist;
  106.     end;
  107.   end;
  108. end;
  109. {--------}
  110. procedure WriteEncodings(aStream    : TSTream;
  111.                      var aEncodings : TEncodingArray;
  112.                          aCount     : integer);
  113. var
  114.   i : integer;
  115.   FlagByte : byte;
  116.   Mask     : byte;
  117. begin
  118.   {build flag byte, write it to the stream}
  119.   FlagByte := 0;
  120.   Mask := 1;
  121.   for i := 0 to pred(aCount) do begin
  122.     if not aEncodings[i].IsChar then
  123.       FlagByte := FlagByte or Mask;
  124.     Mask := Mask shl 1;
  125.   end;
  126.   StreamWrite(aStream, FlagByte, sizeof(FlagByte));
  127.   {write out the encodings}
  128.   for i := 0 to pred(aCount) do begin
  129.     if aEncodings[i].IsChar then
  130.       StreamWrite(aStream, aEncodings[i].AsChar, 1)
  131.     else
  132.       StreamWrite(aStream, aEncodings[i].AsDistLen, 2);
  133.   end;
  134. end;
  135. {--------}
  136. procedure AddCharToEncodings(aStream : TStream;
  137.                                  aCh : char;
  138.                       var aEncodings : TEncodingArray;
  139.                       var aCount     : integer);
  140. begin
  141.   aEncodings[aCount].AsChar := aCh;
  142.   aEncodings[aCount].IsChar := true;
  143.   inc(aCount);
  144.   if (aCount = 8) then begin
  145.     WriteEncodings(aStream, aEncodings, 8);
  146.     aCount := 0;
  147.   end;
  148. end;
  149. {--------}
  150. procedure AddCodeToEncodings(aStream   : TStream;
  151.                              aDistance : integer;
  152.                              aLength   : integer;
  153.                         var aEncodings : TEncodingArray;
  154.                         var aCount     : integer);
  155. begin
  156.   aEncodings[aCount].AsDistLen :=
  157.      (pred(aDistance) shl aalzDistanceShift) + (aLength - 3);
  158.   aEncodings[aCount].IsChar := false;
  159.   inc(aCount);
  160.   if (aCount = 8) then begin
  161.     WriteEncodings(aStream, aEncodings, 8);
  162.     aCount := 0;
  163.   end;
  164. end;
  165. {====================================================================}
  166.  
  167.  
  168. {===Interfaced routines==============================================}
  169. procedure AALZCompress(aInStream, aOutStream : TStream);
  170. var
  171.   HashTable : TaaLZHashTable;
  172.   SlideWin  : TaaLZSlidingWindow;
  173.   Key       : TaaLZKey;
  174.   Offset    : longint;
  175.   CodeCount : integer;
  176.   Encodings : TEncodingArray;
  177.   EnumData  : TEnumExtraData;
  178.   LongValue : longint;
  179.   i         : integer;
  180. begin
  181.   HashTable := TaaLZHashTable.Create;
  182.   try
  183.     SlideWin := TaaLZSlidingWindow.Create(aInStream, true);
  184.     try
  185.       {write the header to the stream: 'AALZ' followed by uncompressed
  186.        size of input stream}
  187.       LongValue := AALZHeader;
  188.       StreamWrite(aOutStream, LongValue, sizeof(LongValue));
  189.       LongValue := aInStream.Size;
  190.       StreamWrite(aOutStream, LongValue, sizeof(LongValue));
  191.       {prepare for the compression}
  192.       CodeCount := 0;
  193.       FillChar(Encodings, sizeof(Encodings), 0);
  194.       {get the first key}
  195.       SlideWin.GetNextKey(Key, Offset);
  196.       {while the key is three characters long...}
  197.       while (length(Key.AsString) = 3) do begin
  198.         {find the longest match in the sliding window using the hash
  199.          table to identify matches}
  200.         EnumData.edSW := SlideWin;
  201.         EnumData.edMaxLen := 0;
  202.         if HashTable.FindAll(Key,
  203.                              Offset - aalzSlidingWindowSize,
  204.                              MatchLongest,
  205.                              @EnumData) then begin
  206.           {we have a match: save the distance/length pair and advance
  207.            the sliding window by the length}
  208.           AddCodeToEncodings(aOutStream,
  209.                              EnumData.edDistMaxMatch,
  210.                              EnumData.edMaxLen,
  211.                              Encodings, CodeCount);
  212.           SlideWin.Advance(EnumData.edMaxLen);
  213.         end
  214.         else begin
  215.           {we don't have a match: save the current character and
  216.            advance by 1}
  217.           AddCharToEncodings(aOutStream,
  218.                              Key.AsString[1],
  219.                              Encodings, CodeCount);
  220.           SlideWin.Advance(1);
  221.         end;
  222.         {now add this key to the hash table}
  223.         HashTable.Insert(Key, Offset);
  224.         {get the next key}
  225.         SlideWin.GetNextKey(Key, Offset);
  226.       end;
  227.       {if the last key was two characters or less, save them as
  228.        literal character encodings}
  229.       if (length(Key.AsString) > 0) then begin
  230.         for i := 1 to length(Key.AsString) do
  231.           AddCharToEncodings(aOutStream,
  232.                              Key.AsString[i],
  233.                              Encodings, CodeCount);
  234.       end;
  235.       {make sure we write out the final encodings}
  236.       if (CodeCount > 0) then
  237.         WriteEncodings(aOutStream, Encodings, CodeCount);
  238.     finally
  239.       SlideWin.Free;
  240.     end;{try..finally}
  241.   finally
  242.     HashTable.Free;
  243.   end;{try..finally}
  244. end;
  245. {--------}
  246. procedure AALZDecompress(aInStream, aOutStream : TStream);
  247. type
  248.   TModeState = (msGetFlagByte, msGetChar, msGetDistLen);
  249. var
  250.   SlideWin      : TaaLZSlidingWindow;
  251.   BytesUnpacked : longint;
  252.   TotalSize     : longint;
  253.   LongValue     : longint;
  254.   ModeState     : TModeState;
  255.   FlagByte      : byte;
  256.   FlagMask      : byte;
  257.   NextChar      : char;
  258.   NextDistLen   : longint;
  259.   CodeCount     : integer;
  260.   Len           : integer;
  261. begin
  262.   SlideWin := TaaLZSlidingWindow.Create(aOutStream, false);
  263.   try
  264.     {read the header from the stream: 'AALZ' followed by uncompressed
  265.      size of input stream}
  266.     StreamRead(aInStream, LongValue, sizeof(LongValue));
  267.     if (LongValue <> AALZHeader) then
  268.       raise Exception.Create('not a AALZ file - no header');
  269.     StreamRead(aInStream, TotalSize, sizeof(TotalSize));
  270.     {prepare for the decompression}
  271.     BytesUnpacked := 0;
  272.     NextDistLen := 0;
  273.     ModeState := msGetFlagByte;
  274.     CodeCount := 0;
  275.     FlagMask := 1;
  276.     {while there are still bytes to decompress...}
  277.     while (BytesUnpacked < TotalSize) do begin
  278.       {read the next item}
  279.       case ModeState of
  280.         msGetFlagByte :
  281.           begin
  282.             StreamRead(aInStream, FlagByte, 1);
  283.             CodeCount := 0;
  284.             FlagMask := 1;
  285.           end;
  286.         msGetChar :
  287.           begin
  288.             StreamRead(aInStream, NextChar, 1);
  289.             SlideWin.AddChar(NextChar);
  290.             inc(BytesUnpacked);
  291.           end;
  292.         msGetDistLen :
  293.           begin
  294.             StreamRead(aInStream, NextDistLen, 2);
  295.             Len := (NextDistLen and aalzLengthMask) + 3;
  296.             SlideWin.AddCode((NextDistLen shr aalzDistanceShift) + 1, Len);
  297.             inc(BytesUnpacked, Len);
  298.           end;
  299.       else
  300.         raise Exception.Create('AALZDecompress.ModeState has an invalid value');
  301.       end;
  302.       {calculate the next mode state}
  303.       inc(CodeCount);
  304.       if (CodeCount > 8) then
  305.         ModeState := msGetFlagByte
  306.       else begin
  307.         if ((FlagByte and FlagMask) = 0) then
  308.           ModeState := msGetChar
  309.         else
  310.           ModeState := msGetDistLen;
  311.         FlagMask := FlagMask shl 1;
  312.       end;
  313.     end;
  314.   finally
  315.     SlideWin.Free;
  316.   end;{try..finally}
  317. end;
  318. {====================================================================}
  319.  
  320.  
  321. end.
  322.